/*
 * alu.c
 *
 *  Created on: Feb 4, 2012
 *      Author: Craig Markham and James Marquardt
 *              Class:  TCSS 371
 *              Assignment:
 */

#include "alu.h"


/*
 * =======================================================================================
 * ALU implementation
 * =======================================================================================
 */

/**
 * Returns an ALU pointer with space for the 4 registers and 3 ints.
*/
ALU * alu_init(ALU * alu_ptr) {
  return alu_ptr = (ALU *) malloc(4 * sizeof(ushort) + 7 * sizeof(int));
}

/**
 * Sets flags to 0
*/
void alu_clearFlags(ALU * alu_ptr) {
  alu_ptr->zero_flag = 0;
  alu_ptr->carry_flag = 0;
  alu_ptr->negative_flag = 0;
  alu_ptr->overflow_flag = 0;
  alu_ptr->positive_flag = 0;
}

/**
 * SW register points at the status word
*/
void alu_setSWReference(ALU * alu_ptr, Register sw_ref) {
  alu_ptr->SW = sw_ref;
}

/**
 * Sets Z flag int.
*/
void alu_setZeroFlag(ALU * alu_ptr) {
  alu_ptr->zero_flag = 1;
}

void alu_setPositiveFlag(ALU * alu_ptr) {
    alu_ptr->positive_flag = 1;
}

/*
 * Sets N flag.
 */
void alu_setNegFlag(ALU * alu_ptr) {
  alu_ptr->negative_flag = 1;
}

/*
 * Sets C flag.
 */ 
void alu_setCarryFlag(ALU * alu_ptr) {
  alu_ptr->carry_flag = 1;
}

/*
 * Sets O flag.
 */
void alu_setOverflowFlag(ALU * alu_ptr) {
  alu_ptr->overflow_flag = 1;
}

/**
 * Puts value in a register into the ALU Register A
*/
void alu_putOperandA(ALU * alu_ptr, Register reg) {
  int * error = 0;
  register_putRegValue(&alu_ptr->register_a, register_getRegValue(&reg, * error));
}

/**
 * Puts value in a register into the ALU Register B
*/
void alu_putOperandB(ALU * alu_ptr, Register reg) {
  int * error = 0;
  register_putRegValue(&alu_ptr->register_b, register_getRegValue(&reg, * error));
}

/**
 * Sets opcodes passed in from CPU. First five bits primary, other if opcode overloaded, null otherwise
*/
void alu_getOperation(ALU * alu_ptr, int primary, int overload) {
  alu_ptr->primary_op = primary;
  alu_ptr->overload_op = overload;
}

/**
 * Perfoms the ALU operation as determined by the primary and overload op registers
*/
void alu_perform_operation_primary(ALU * alu_ptr, int * error) {
  alu_clearFlags(alu_ptr);

  switch (alu_ptr->primary_op) {

    case OVERLOAD_OP:
      alu_perform_operation_secondary(alu_ptr, error);
      break;

    case SHL_OP:
      alu_shl(alu_ptr, error);
      break;

    case SHR_OP:
      alu_shr(alu_ptr, error);
      break;

    default:
      break;
    }
}

/**
 * Performs secondary ALU op, if necessary according to primary op register.
*/
void alu_perform_operation_secondary(ALU * alu_ptr, int * error) {

  alu_clearFlags(alu_ptr);

  switch (alu_ptr->overload_op) {
    case ADD_OP:
      alu_add(alu_ptr, error);
      break;

    case SUB_OP:
      alu_sub(alu_ptr, error);
      break;

    case MUL_OP:
      alu_mul(alu_ptr, error);
      break;

    case DIV_OP:
      alu_div(alu_ptr, error);
      break;

    case AND_OP:
      alu_and(alu_ptr, error);
      break;

    case OR_OP:
      alu_or(alu_ptr, error);
      break;

    case XOR_OP:
      alu_xor(alu_ptr, error);
      break;

    case NOT_OP:
      alu_not(alu_ptr, error);
      break;

    default:
      break;
  }

  //set neg flag
  if ((register_getRegValue(&alu_ptr->register_r, *error) & 0x8000) == 0x8000) {
    alu_setFlags(alu_ptr, 0x8000);
  }

  //set zero flag
  else if (register_getRegValue(&alu_ptr->register_r, *error) == 0) {
    alu_setFlags(alu_ptr, 0x4000);
  }

}

/**
 * ALU add operation
*/
void alu_add(ALU * alu_ptr, int * error) {

  register_putRegValue(&alu_ptr->register_r, (register_getRegValue(&alu_ptr->register_a, *error) +
    register_getRegValue(&alu_ptr->register_b, *error)));
/*
  if(register_signOf(&alu_ptr->register_a) == register_signOf(&alu_ptr->register_b) &&
    register_signOf(&alu_ptr->register_r) != register_signOf(&alu_ptr->register_a)) {
    alu_setFlags(alu_ptr, 0x1000);
    }

  if(register_signOf(&alu_ptr->register_a) == 1 && register_signOf(&alu_ptr->register_b) == 1 &&
    register_signOf(&alu_ptr->register_r) == 0) {
    alu_setFlags(alu_ptr, 0x2000);
  }
*/
}

/**
 * ALU sub operation
*/
void alu_sub(ALU * alu_ptr, int * error) {

  register_putRegValue(&alu_ptr->register_r, (register_getRegValue(&alu_ptr->register_a, *error) -
    register_getRegValue(&alu_ptr->register_b, *error)));
    
    if (alu_ptr->register_r == 0) {
        alu_setZeroFlag(alu_ptr);
    }
    
    if (alu_ptr->register_r > 0) {
        alu_setPositiveFlag(alu_ptr);
    }

}

/**
 * ALU multiply operation
*/
void alu_mul(ALU * alu_ptr, int * error) {

  register_putRegValue(&alu_ptr->register_r, (register_getRegValue(&alu_ptr->register_a, *error) *
    register_getRegValue(&alu_ptr->register_b, *error)));

}

/**
 * ALU divide operation
*/
void alu_div(ALU * alu_ptr, int * error) {

  register_putRegValue(&alu_ptr->register_r, (register_getRegValue(&alu_ptr->register_a, *error) /
    register_getRegValue(&alu_ptr->register_b, *error)));

}

/**
 * ALU and operation
*/
void alu_and(ALU * alu_ptr, int * error) {

  register_putRegValue(&alu_ptr->register_r, (register_getRegValue(&alu_ptr->register_a, *error) &
    register_getRegValue(&alu_ptr->register_b, *error)));

    if (alu_ptr->register_r == 0) {
        alu_setZeroFlag(alu_ptr);
    }
    
    if (alu_ptr->register_r > 0) {
        alu_setPositiveFlag(alu_ptr);
    }
}

/**
 * ALU or operation
*/
void alu_or(ALU * alu_ptr, int * error) {

  register_putRegValue(&alu_ptr->register_r, (register_getRegValue(&alu_ptr->register_a, *error) |
    register_getRegValue(&alu_ptr->register_b, *error)));

}

/**
 * ALU xor operation
*/
void alu_xor(ALU * alu_ptr, int * error) {

  register_putRegValue(&alu_ptr->register_r, (register_getRegValue(&alu_ptr->register_a, *error) ^
    register_getRegValue(&alu_ptr->register_b, *error)));

}

/**
 * ALU not operation
*/
void alu_not(ALU * alu_ptr, int * error) {

  register_putRegValue(&alu_ptr->register_r, (~register_getRegValue(&alu_ptr->register_a, *error)));

}

/**
 * ALU shift left operation
*/
void alu_shl(ALU * alu_ptr, int * error) {

  register_putRegValue(&alu_ptr->register_r, (register_getRegValue(&alu_ptr->register_b, *error) << 1));

}

/**
 * ALU shift right operation
*/
void alu_shr(ALU * alu_ptr, int * error) {

  register_putRegValue(&alu_ptr->register_r, (register_getRegValue(&alu_ptr->register_b, *error) >> 1));

}

void alu_setFlags(ALU * alu_ptr, int  error){

}

/*
 * =======================================================================================
 * Memory implementation
 * =======================================================================================
 */

/**
 * Memory Module initialization
 * creates user memory space and fills with random numbers
*/
MemoryModulePtr memory_initialize(){
  MemoryModulePtr mm;
  int i = 0;
  srand(time(NULL));
  mm = (uchar *) malloc(MEMORY_SIZE * sizeof(uchar));
  for(i = 0; i <=  MEMORY_SIZE; i++){
    mm[i] = rand() % 0xFF;
  }
  return mm;
}

/**
 * Memory Module fetchByteFrom
 * returns a byte form address, sets error code
*/
uchar memory_fetchByteFrom(MemoryModulePtr mm, ushort address, int * error){
  uchar val = -1;
  ushort index = address - 0x3000;
    if (index > -1 && index < MEMORY_SIZE) {
        val = mm[index];
    }
    else
      * error = ARRAY_OOB;
    return val;
}

/**
 * Memory Module storeByteTo
 * stores byte at address, sets error code
*/
int memory_storeByteTo(MemoryModulePtr mm, ushort address, uchar byte){
  int error = -1;
  ushort index = address - 0x3000;
    if (index > -1 && index < MEMORY_SIZE) {
      mm[index] = byte;
    }
    else
      error = ARRAY_OOB;
    return error;
}

/**
 * Memory Module fetchWordFrom
 * returns the contents of two consecutive locations starting at address. sets error code
*/
ushort memory_fetchWordFrom(MemoryModulePtr mm, ushort address, int * error){
  ushort val = -1;
  uchar lob;
  uchar hob;
  ushort index = address - 0x3000;
    if (index > -1 && index < MEMORY_SIZE) {
      hob = mm[index];
      lob = mm[index + 1];
      val = ((ushort) hob) << 8 | lob;
    }
    else
      * error = ARRAY_OOB;
    return val;
}

/**
 * Memory Module storeWordTo
 * stores the contents at two consecutive locations starting at address. returns error code
*/
ushort memory_storeWordTo(MemoryModulePtr mm, ushort address, ushort word){
  int error = -1;
  ushort index = address - 0x3000;
  uchar lob = (uchar) word;
  uchar hob = (uchar) (word >> 8);

    if (index > -1 && index < MEMORY_SIZE) {
      mm[index] = hob;
      mm[index + 1] = lob;
    }
    else
      error = ARRAY_OOB;
    return error;
}

